package dev.ece.suit.loader.models.dae;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;

import dev.ece.suit.loader.models.dae.DAEControllers.Controller;
import dev.ece.suit.loader.models.dae.DAEGeometries.Input;
import dev.ece.suit.loader.models.dae.DAEGeometries.Source;
import dev.ece.suit.loader.models.dae.DAEGeometries.Triangles;
import dev.ece.util.lib.Matrix4f;
import dev.ece.util.lib.Vector2f;
import dev.ece.util.lib.Vector3f;
import dev.ece.util.lib.Vector4f;
import dev.ece.util.tools.Constants;

public class DAETriangles {
	
	private Vector3f[] vertice;
	
	private Vector3f[] normals;
	
	private Vector2f[] textureCoords;

	private Vector4f[] bones;
	
	private Vector4f[] weights;
	
	private DAETriangle[][] triangles;
	
	private DAETriangles() {}
	
	public static DAETriangles createDAETriangles(DAEGeometries.Mesh mesh, DAEJoint[] joints, Controller controller) throws IOException {
		Matrix4f bindShapeMatrix = controller != null ? controller.getSkin().toBindShapeMatrix() : new Matrix4f();
		if(controller == null) {
			bindShapeMatrix.identity();
		}
		DAETriangles triangles = new DAETriangles();
		Map<String, Source> sources = mesh.toSourceMap();
		//排序
		Source[] orderedSource = new Source[3];
		int[] offsets = new int[3];
		Input[] inputs = mesh.getTriangles()[0].getInput();
		for(Input input: inputs) {
			if(StringUtils.equals(DAE.VERTEX, input.getSemantic())) {
				offsets[0] = input.getOffset();
				orderedSource[0] = sources.get(input.getSource());
			} else if(StringUtils.equals(DAE.NORMAL, input.getSemantic())) {
				offsets[1] = input.getOffset();
				orderedSource[1] = sources.get(input.getSource());
			} else if(StringUtils.equals(DAE.TEXCOORD, input.getSemantic())) {
				offsets[2] = input.getOffset();
				orderedSource[2] = sources.get(input.getSource());
			} 
		}
		triangles.triangles = new DAETriangle[mesh.getTriangles().length][];
		triangles.vertice = orderedSource[0].toVector3f(bindShapeMatrix);
		triangles.normals = orderedSource[1].toVector3f();
		triangles.textureCoords = orderedSource[2].toVector2f();
		
		createBoneWeight(triangles, joints, controller);
		
		//三角面
		int size = DAETriangle.SIZE * 3;
		String[] numArray = null;
		DAETriangle daetriangle = null;
		Triangles triangle = null;
		int index = 0;
		for(int row = 0; row<triangles.triangles.length; row++) {
			triangle = mesh.getTriangles()[row];
			triangles.triangles[row] = new DAETriangle[triangle.getCount()];
			numArray = StringUtils.split(StringUtils.trim(Constants.inline(triangle.getP()).toString()), " ");
			inputs = triangle.getInput();
			for(Input input: inputs) {
				if(StringUtils.equals(DAE.VERTEX, input.getSemantic())) {
					offsets[0] = input.getOffset();
				} else if(StringUtils.equals(DAE.NORMAL, input.getSemantic())) {
					offsets[1] = input.getOffset();
				} else if(StringUtils.equals(DAE.TEXCOORD, input.getSemantic())) {
					offsets[2] = input.getOffset();
				} 
			}
			index = 0;
			for(int i=0; i<triangle.getCount(); i++) {
				daetriangle = new DAETriangle();
				daetriangle.getIndice()[0] = NumberUtils.toInt(numArray[index + offsets[0]]);
				daetriangle.getIndice()[1] = NumberUtils.toInt(numArray[index + offsets[0] + DAETriangle.SIZE]);
				daetriangle.getIndice()[2] = NumberUtils.toInt(numArray[index + offsets[0] + 2 * DAETriangle.SIZE]);
				
				daetriangle.getNormalIndice()[0] = NumberUtils.toInt(numArray[index + offsets[1]]);
				daetriangle.getNormalIndice()[1] = NumberUtils.toInt(numArray[index + offsets[1] + DAETriangle.SIZE]);
				daetriangle.getNormalIndice()[2] = NumberUtils.toInt(numArray[index + offsets[1] + 2 * DAETriangle.SIZE]);
				
				daetriangle.getTextureCoordindice()[0] = NumberUtils.toInt(numArray[index + offsets[2]]);
				daetriangle.getTextureCoordindice()[1] = NumberUtils.toInt(numArray[index + offsets[2] + DAETriangle.SIZE]);
				daetriangle.getTextureCoordindice()[2] = NumberUtils.toInt(numArray[index + offsets[2] + 2 * DAETriangle.SIZE]);
				triangles.triangles[row][i] = daetriangle;
				index += size;
			}
		}
		return triangles;
	}
	
	protected static void createBoneWeight(DAETriangles triangles, DAEJoint[] joints, Controller controller) throws IOException {
		//骨骼绑定、权重
		int len = triangles.vertice.length;
		triangles.bones = new Vector4f[len];
		triangles.weights = new Vector4f[len];
		if(joints != null && controller != null) {
			Map<String, Source> skinSources = controller.getSkin().createSourceMap();
			Source[] orderedJointsSource = new Source[2]; 
			Input[] inputs = null;
			if(controller.getSkin().getJoints() != null) {
				inputs = controller.getSkin().getJoints().getInput();
				if(inputs != null) {
					DAEGeometries.Source source = null;
					for(Input input: inputs) {
						source = skinSources.get(input.getSource());
						if(source != null) {
							if(StringUtils.equals(DAE.JOINT, input.getSemantic())) {
								orderedJointsSource[0] = source;
							}
						}
					}
				}
			}
			int[] offsets = new int[2];
			if(controller.getSkin().getVertex_weights() != null) {
				inputs = controller.getSkin().getVertex_weights().getInput();
				if(inputs != null) {
					DAEGeometries.Source source = null;
					for(Input input: inputs) {
						source = skinSources.get(input.getSource());
						if(source != null) {
							if(StringUtils.equals(DAE.JOINT, input.getSemantic())) {
								orderedJointsSource[0] = source;
								offsets[0] = input.getOffset();
							} else if(StringUtils.equals(DAE.WEIGHT, input.getSemantic())) {
								orderedJointsSource[1] = source;
								offsets[1] = input.getOffset();
							}
						}
					}
				}
			}
			Map<String, Integer> nameJointIndexMap = new ConcurrentHashMap<String, Integer>();
			String[] jointName = orderedJointsSource[0].toStringArray();
			float[] weights = orderedJointsSource[1].toFloatArray();
			for(int i=0; i<joints.length; i++) {
				nameJointIndexMap.put(joints[i].getName(), i);
			}
			int[] vcount = controller.getSkin().getVertex_weights().toVCountArray();
			int[] v = controller.getSkin().getVertex_weights().toVArray();
			int index = 0;
			int size = offsets.length;
			int count = 0;
			Vector4f bone = null;
			Vector4f weight = null;
			for(int i=0; i<vcount.length; i++) {
				count = vcount[i];
				bone = new Vector4f(-1, -1, -1, -1);
				weight = new Vector4f();
				triangles.bones[i] = bone;
				triangles.weights[i] = weight;
				for(int j=0; j<count; j++) {
					bone.getVector()[j] = nameJointIndexMap.get(jointName[v[index + offsets[0]]]);
					weight.getVector()[j] = weights[v[index + offsets[1]]];
					index += size;
				}
			}
		} else {
			for(int i=0; i<len; i++) {
				triangles.bones[i] = new Vector4f(-1, -1, -1, -1);
				triangles.weights[i] = new Vector4f();
			}
		}
	}
	
	public List<DAEMesh> toDAEMesh(DAEGeometries.Mesh mesh, Map<String, Integer> materialIndexMap) {
		List<DAEMesh> meshes = new ArrayList<DAEMesh>();
		DAEMesh daemesh = null;
		Integer materialIndex = null;
		for(int i=0; i<this.triangles.length; i++) {
			materialIndex = materialIndexMap.get(StringUtils.join("#", mesh.getTriangles()[i].getMaterial()));
			daemesh = new DAEMesh(new int[] {}, materialIndex != null ? materialIndex : -1);
			fillDAEMesh(daemesh, this.triangles[i]);
			meshes.add(daemesh);
		}
		return meshes;
	}
	
	public void boundBox(Vector3f min, Vector3f max, boolean first) {
		if(first) {
			min.set(this.vertice[0]);
			max.set(this.vertice[1]);
		}
		for(Vector3f vertex: this.vertice) {
			min.set(
					Math.min(vertex.getX(), min.getX()), 
					Math.min(vertex.getY(), min.getY()), 
					Math.min(vertex.getZ(), min.getZ()));
			max.set(
					Math.max(vertex.getX(), max.getX()), 
					Math.max(vertex.getY(), max.getY()), 
					Math.max(vertex.getZ(), max.getZ()));
		}
	}
	
	public void fillDAEMesh(DAEMesh mesh, DAETriangle[] triangles) {
		for(DAETriangle triangle: triangles) {
			triangle.fillMesh(mesh, this);
		}
	}
	
	public Vector3f[] getVertice() {
		return vertice;
	}

	public Vector3f[] getNormals() {
		return normals;
	}

	public Vector2f[] getTextureCoords() {
		return textureCoords;
	}

	public Vector4f[] getBones() {
		return bones;
	}

	public Vector4f[] getWeights() {
		return weights;
	}

	public DAETriangle[][] getTriangles() {
		return triangles;
	}

}
